// Copyright (c) 2021-2022 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { PandaGen } from "./pandagen"
% def get_node_kind(mnemonic)
%   return "#{mnemonic.gsub('.', '_').upcase}"
% end
%
% def insn2node(insn)
%   mnemonic = insn.mnemonic.split('.')
%   return mnemonic.map{|el| el == '64' ? 'Wide' : el.capitalize}.join()
% end
%
% def get_result_type(insn)
%   if insn.mnemonic.start_with? "call"
%     return "ResultType.Unknown"
%   end
%   case insn.dtype
%   when 'i32'
%     return "ResultType.Int"
%   when 'i64', 'b64'
%     return "ResultType.Long"
%   when 'f64'
%     return "ResultType.Float"
%   when 'obj'
%     return "ResultType.Obj"
%   else
%     return "ResultType.None"
%   end
% end
%
% def get_result_dst(insn)
%   if insn.properties.include? "acc_write"
%     return "ResultDst.Acc"
%   end
%   dst_operands = insn.operands.select { |op| op.dst? }
%   if dst_operands.length > 0 and dst_operands[0].reg?
%     return "ResultDst.VReg"
%   end
%   return "ResultDst.None"
% end
%
% def is_VReg(name)
%     if name ==  :v
%        return true
%     end
% end
%
% def is_Acc(name)
%    if name ==  :acc
%        return true
%     end
% end
%
% def is_Imm(name)
%    if name ==  :imm
%        return true
%     end
% end
%
% def is_Id(name)
%     if %i[method_id type_id field_id string_id literalarray_id callsite_id].include?(name)
%        return true
%     end
% end
%
% def is_Call(insn)
%   if insn.mnemonic.start_with? "call"
%      return true
%   end
%   return false
% end
%
% def is_CallRange(insn)
%   if insn.mnemonic == "callrange" or insn.mnemonic == "wide.callrange" or
%       insn.mnemonic == "callthisrange" or insn.mnemonic == "wide.callthisrange" or
%       insn.mnemonic == "newobjrange" or insn.mnemonic == "wide.newobjrange" or
%       insn.mnemonic == "createobjectwithexcludedkeys" or insn.mnemonic == "wide.createobjectwithexcludedkeys" or
%       insn.mnemonic == "supercallthisrange" or insn.mnemonic == "wide.supercallthisrange" or
%       insn.mnemonic == "supercallarrowrange" or insn.mnemonic == "wide.supercallarrowrange"
%      return true
%   end
%   return false
% end
%
% def get_operand_type(name, insn)
%   if is_VReg(name)
%     return "VReg"
%   elsif is_Imm(name)
%     is_jump = insn.properties.include? 'jump'
%     return is_jump ? "Label" : "Imm"
%   elsif is_Id(name)
%     return "string"
%   else
%     return nil
%   end
% end
%
% def get_operands(sig)
%     return [] unless sig.include? ' '
%     _, operands = sig.match(/(\S+) (.+)/).captures
%     operands = operands.split(', ')
%     end
%
% def get_ctor_args(insn)
%     operands = get_operands(insn.sig)
%     ops = Array.new
%     ctor_args = Array.new
%     operands.map do |operand|
%     operand_parts = operand.split(':')
%       case operand_parts.size
%       when 1
%         name = operand_parts[0]
%       when 2
%         name, _ = operand_parts
%       when 3
%         name, _, _ = operand_parts
%       else
%         raise 'Unexpected operand string'
%       end
%       ops.push(name)
%       name_tmp = name.to_s.gsub(/[0-9]/, '').to_sym
%       type = get_operand_type(name_tmp,insn)
%       if is_Call(insn) and !is_CallRange(insn) and is_VReg(name_tmp) and !insn.mnemonic.include?('acc')
%          ctor_args.push("#{name}?: #{type}")
%       else
%          ctor_args.push("#{name}: #{type}")
%       end
%     end
%     return ops,ctor_args
% end
%
% def get_operand_kind(op, insn)
%   if op.reg?
%     if op.src? and op.dst?
%       return 2
%     elsif op.src?
%       return 0
%     elsif op.dst?
%       return 1
%     end
%     return nil
%   elsif op.imm?
%     is_jump = insn.properties.include? 'jump'
%     return is_jump ? 6 : 3
%  elsif op.id?
%    is_string_id = insn.properties.include? 'string_id'
%    return is_string_id ? 5 : 4
%  else
%    return nil
%  end
% end
%
% def make_format(fmt, insn)
%   operands = fmt.operands.map {|op| "[#{get_operand_kind(op, insn)}, #{op.width}]"}
%   return "[" + operands.join(", ") + "]"
% end
%
// Autogenerated file -- DO NOT EDIT!
import * as ts from "typescript";
import {
  DebugPosInfo,
  NodeKind
} from "./debuginfo";
import { Scope } from "./scope";

export enum IRNodeKind {
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
  <%= get_node_kind(mnemonic) %>,
% end
  VREG,
  IMM,
  LABEL,
  VIRTUALSTARTINS_DYN,
  VIRTUALENDINS_DYN,
  DEFINE_GLOBAL_VAR,
}

export function getInstructionSize(opcode: IRNodeKind) {
  switch(opcode) {
% Panda::instructions.each do |insn|
% node_kind = get_node_kind(insn.mnemonic)
% kind = "IRNodeKind." + node_kind
    case <%= kind %>:
      return <%= insn.format.size %>;
% end
    default:
      // LOGE("getInstructionSize: Unknown opcode:" + opcode);
      return 0;
  }
}

export enum ResultType {
  None,
  Unknown,
  Int,
  Long,
  Float,
  Obj,
  Boolean
}

export enum ResultDst {
  None,
  Acc,
  VReg
}

export enum BuiltIns {
  NaN,
  Infinity,
  globalThis,
  undefined,
  Boolean,
  Number,
  String,
  BigInt,
  Symbol,
  Null,
  Object,
  Function,
  Global,
  True,
  False,
  LexEnv,
  MAX_BUILTIN,
}

export type OperandType = VReg | Imm | Label | string | number

export enum OperandKind {
  // the least significant bit indicates vreg
  // the second bit indicates src or dst
  SrcVReg = 0, DstVReg, SrcDstVReg, Imm, Id, StringId, Label
}

export namespace OperandKind {
  // @ts-ignore
  function isVReg(kind: OperandKind): boolean {
    return kind === OperandKind.SrcVReg || kind === OperandKind.DstVReg || kind === OperandKind.SrcDstVReg;
  }
}

export type Format = number[][]

export function getInsnMnemonic(opcode: IRNodeKind): string {
  switch(opcode) {
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
% node_kind = get_node_kind(mnemonic)
% kind = "IRNodeKind." + node_kind
    case <%= kind %>:
      return "<%= mnemonic %>";
% end
    default:
      return '';
  }
}

export function getInsnFormats(opcode: IRNodeKind) {
  switch(opcode) {
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
% insn = group.first
% formats = group.map {|i| make_format(i,insn) }
% node_kind = get_node_kind(mnemonic)
% kind = "IRNodeKind." + node_kind
    case <%= kind %>: {
      return [
        <%= formats.join(",\n        ") %>
      ];
    }
% end
    default:
      return [];
  }
}

export abstract class IRNode {
  private node: ts.Node | NodeKind = NodeKind.Normal;
  public static pg: PandaGen | undefined = undefined;
  constructor(
    readonly kind: IRNodeKind,
    readonly operands: OperandType[],
  ) {}
  // for debuginfo
  public debugPosInfo: DebugPosInfo = new DebugPosInfo();

  toString(): string {
    let mnemonic = this.getMnemonic();
    let out = mnemonic + "\t";
    if (mnemonic.length < 8) {
      out += "\t";
    }

    this.operands.forEach((element) => {
      out = out + element.toString() + ", ";
    });

    return out;
  }

  setNode(node: ts.Node | NodeKind) {
    this.node = node;
  }

  getNodeName() {
    if (this.node != NodeKind.Invalid &&
        this.node != NodeKind.FirstNodeOfFunction &&
        this.node != NodeKind.Normal) {
      return ts.SyntaxKind[(<ts.Node>this.node).kind];
    }

    return "undefined";
  }

  getMnemonic() {
    return getInsnMnemonic(this.kind);
  }

  getFormats() {
    return getInsnFormats(this.kind);
  }

  static setPandagen(pg: PandaGen) {
    IRNode.pg = pg;
  }
}

export class VReg {
  private typeIndex: number | undefined;
  private variableName: string | undefined;
  num: number = -1;
  
  toString(): string {
      return "V" + this.num;
  }

  constructor() {
  }

  getTypeIndex() {
    return this.typeIndex;
  }

  setTypeIndex(typeIndex: number) {
    this.typeIndex = typeIndex;
  }

  getVariableName() {
    return this.variableName;
  }

  setVariableName(variableName: string) {
    this.variableName = variableName;
  }
}

export class Imm extends IRNode {
  readonly value: number;

  constructor(value: number) {
    super(IRNodeKind.IMM, []);
    this.value = value;
  }

  toString(): string {
    return "#" + this.value;
  }
}

export class Label extends IRNode {
  private static global_id = 0;
  readonly id: number;

  constructor() {
    super(IRNodeKind.LABEL, []);
    this.id = Label.global_id++;
  }

  toString(): string {
    return "LABEL_" + this.id;
  }
}

export class DebugInsStartPlaceHolder extends IRNode {
  private scope: Scope;

  constructor(scope: Scope) {
    super(IRNodeKind.VIRTUALSTARTINS_DYN, []);
    this.scope = scope;
  }

  getScope() {
    return this.scope;
  }
}

export class DebugInsEndPlaceHolder extends IRNode {
  private scope: Scope;

  constructor(scope: Scope) {
    super(IRNodeKind.VIRTUALENDINS_DYN, []);
    this.scope = scope;
  }

  getScope() {
    return this.scope;
  }
}

% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
% insn = group.first
% jump = insn.properties.include? 'jump'
% node_kind = get_node_kind(mnemonic)
% kind = "IRNodeKind." + node_kind
% ops_list,ctor_arg_list = get_ctor_args(insn)
% ctor_args = ctor_arg_list.map {|arg| "#{arg}"}.join(", ")
% ops = ops_list.map { |op| "#{op}"}.join(", ")
% result_type = get_result_type(insn)
% result_dst = get_result_dst(insn);
% is_call_op = is_Call(insn)
% is_callrange_op = is_CallRange(insn)
% parent_type = "IRNode"
export class <%= insn2node(insn) %> extends <%= parent_type %> {
% if is_callrange_op
%   op_last = ops_list.pop
%   ops_length = ops_list.size
  constructor(<%= ctor_arg_list.join(", ")%>[]) {
    var ctors = [<%=ops_list.join(", ")%>, ...<%=op_last %>]
    var operands:OperandType[] = [<%=ops_list.join(", ")%>]
%   for i in 1..ops_length do
    ctors.shift()
%   end
    while (!!(ctors && ctors.length)){
      let ctor = ctors.shift()
      if (ctor != undefined) {
        operands.push(ctor)
      }
    }
% else
  constructor(<%= ctor_args %>) {
% if is_call_op and ops.length() != 0
    var ctors = [<%= ops %>]
    var operands:OperandType[] = [<%=ops_list[0] %>]
    ctors.shift()
    while (!!(ctors && ctors.length)){
      let ctor = ctors.shift()
      if (ctor != undefined) {
        operands.push(ctor)
      }
    }
% end
% end
    super(
      <%= kind %>,
% if is_call_op or is_callrange_op
      operands,
% else
      [<%= ops %>]
% end
    );
% is_jit_ic = insn.properties.include?("jit_ic_slot")
% is_ic = (!is_jit_ic) && insn.properties.include?("ic_slot")
% if (is_ic)
%   ret = insn.properties.include?("one_slot") ? 1 : 2;
    let invalid = 255;
    let icSize = (<PandaGen>(IRNode.pg)).getIcSize();
    if (icSize <= invalid) {
        if ((icSize + <%= ret %>) > 255)  {
            this.operands[0] = new Imm(256);
            let inc = <%= ret %> + 256 - icSize;
            (<PandaGen>(IRNode.pg)).updateIcSize(inc);
        } else {
            this.operands[0] = new Imm(icSize);
            (<PandaGen>(IRNode.pg)).updateIcSize(<%= ret %>);
        }
    } else if(icSize > invalid && icSize <= 65535) {
        this.operands[0] = new Imm(icSize);
        (<PandaGen>(IRNode.pg)).updateIcSize(<%= ret %>);
    } else {
        this.operands[0] = new Imm(invalid);
    }
% end
% if (is_jit_ic && !is_ic)
%   ret = insn.properties.include?("one_slot") ? 1 : 2;
    let invalid = 255;
    let icSize = (<PandaGen>(IRNode.pg)).getIcSize();
    if (icSize < invalid) {
        if ((icSize + <%= ret %>) > invalid) {
            this.operands[0] = new Imm(invalid);
            (<PandaGen>(IRNode.pg)).updateIcSize((256 - icSize));
        } else {
            this.operands[0] = new Imm(icSize);
            (<PandaGen>(IRNode.pg)).updateIcSize(<%= ret %>);
        }
    } else {
        this.operands[0] = new Imm(invalid);
    }
% end
  }
% if jump
% target_index = insn.operands.index {|op| op.imm? }

  getTarget(): Label {
    return <Label> this.operands[<%= target_index %>];
  }
% end
}

% end
